home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / kernel / fs / fsInit.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-12-19  |  16.4 KB  |  621 lines

  1. /* 
  2.  * fsInit.c --
  3.  *
  4.  *    Filesystem initializtion.  This is done by setting up the
  5.  *    initial process and then having all other processes
  6.  *    inherit things.
  7.  *
  8.  * Copyright (C) 1987 Regents of the University of California
  9.  * All rights reserved.
  10.  * Permission to use, copy, modify, and distribute this
  11.  * software and its documentation for any purpose and without
  12.  * fee is hereby granted, provided that the above copyright
  13.  * notice appear in all copies.  The University of California
  14.  * makes no representations about the suitability of this
  15.  * software for any purpose.  It is provided "as is" without
  16.  * express or implied warranty.
  17.  */
  18.  
  19. #ifndef lint
  20. static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/fs/fsInit.c,v 9.16 91/09/10 18:22:31 rab Exp $ SPRITE (Berkeley)";
  21. #endif not lint
  22.  
  23.  
  24. #include <sprite.h>
  25.  
  26. #include <fs.h>
  27. #include <fsutil.h>
  28. #include <fsNameOps.h>
  29. #include <fsio.h>
  30. #include <fsprefix.h>
  31. #include <fslcl.h>
  32. #include <devFsOpTable.h>
  33. #include <fspdev.h>
  34. #include <fsStat.h>
  35. #include <fsconsist.h>
  36. #include <proc.h>
  37. #include <rpc.h>
  38. #include <recov.h>
  39. #include <timer.h>
  40. #include <fsdm.h>
  41. #include <fsrmt.h>
  42. #include <devTypes.h>
  43. #include <strings.h>
  44. #include <stdlib.h>
  45. #include <stdio.h>
  46.  
  47.  
  48. #define    SCSI_MAKE_DEVICE_TYPE(type, hbaType, ctrlNum, targetID, LUN, dBits) \
  49.         (((hbaType)<<8)|(type))
  50. #define    SCSI_MAKE_DEVICE_UNIT(type, hbaType, ctrlNum, targetID, LUN, dBits)  \
  51.         (((ctrlNum)<<10)|((LUN)<<7)|((targetID)<<4)|(dBits))
  52.  
  53. /*
  54.  * The prefix under which the local disk is attached.  
  55.  */
  56. #define LOCAL_DISK_NAME        "/"
  57. int fsDefaultDomainNumber = 0;
  58.  
  59. /*
  60.  * We record the maximum transfer size supported by the RPC system
  61.  * for use in chopping up remote I/O operations.
  62.  *
  63.  * If these are initialized to zero they will be set to the maximum
  64.  * sizes reported back by the RPC system.
  65.  */
  66. int fsMaxRpcDataSize = 0;    /* Used to be 4096 */
  67. int fsMaxRpcParamSize = 0;    /* Used to be 1024 */
  68.  
  69. /*
  70.  * Statistics structure.
  71.  */
  72. Fs_Stats    fs_Stats;
  73.  
  74. /*
  75.  * Timer queue element for updating time of day.
  76.  */
  77.  
  78. Timer_QueueElement    fsutil_TimeOfDayElement;
  79.  
  80. /* 
  81.  * Flag to make sure we only do certain things, such as syncing the disks,
  82.  * after the file system has been initialized.
  83.  */
  84. Boolean fsutil_Initialized = FALSE;
  85.  
  86. /*
  87.  * Flag to indicate whether we have attached a disk or not.  We can use
  88.  * this to determine if we are a client or a server.
  89.  */
  90.  
  91. Boolean fsDiskAttached = FALSE;
  92.  
  93.  
  94. void Fs_Init()
  95. {
  96.     Fs_InitData();
  97.     Fs_InitNameSpace();
  98. }
  99. /*
  100.  *----------------------------------------------------------------------
  101.  *
  102.  * Fs_InitData
  103.  *
  104.  *    Initialize most filesystem data structures.
  105.  *
  106.  * Results:
  107.  *    None.
  108.  *
  109.  * Side effects:
  110.  *    Data structures in the file system are initialized.
  111.  *
  112.  *----------------------------------------------------------------------
  113.  */
  114. void
  115. Fs_InitData()
  116. {
  117.     /*
  118.      * Initialized the known domains. Local, Remote, and Pfs.
  119.      */
  120.     Fslcl_NameInitializeOps();
  121.     Fsio_InitializeOps();
  122.     Fsrmt_InitializeOps();
  123.     Fspdev_InitializeOps();
  124.  
  125.     bzero((Address) &fs_Stats, sizeof(Fs_Stats));
  126.     fs_Stats.statsVersion = FS_STAT_VERSION;
  127.  
  128.     /*
  129.      * The handle cache and the block cache start out with a hash table of
  130.      * a given size which grows on demand.  Thus the numbers passed to
  131.      * the next two routines are not crucial.
  132.      */
  133.     Fscache_Init(64);
  134.     Fsutil_HandleInit(64);
  135.     Fsprefix_Init();
  136.     Fsconsist_ClientInit();
  137.     Fslcl_DomainInit();
  138.     return;
  139. }
  140.  
  141.  
  142. /*
  143.  *----------------------------------------------------------------------
  144.  *
  145.  * Fs_InitNameSpace
  146.  *
  147.  *    Initialize the filesystem name space.
  148.  *    This does the first steps in boot-strapping
  149.  *    the name space.  The prefix table is primed with "/", and
  150.  *    a local disk is attached under "/bootTmp" if possible.  Later
  151.  *    in Fs_ProcInit "/bootTmp" gets promoted to root if noone
  152.  *    else is around to serve root.
  153.  *
  154.  *    This also initializes the file system's time (which could
  155.  *    perhaps be replaced someday).  This has to be done after
  156.  *    Rpc_Start.
  157.  *
  158.  * Results:
  159.  *    None.
  160.  *
  161.  * Side effects:
  162.  *    "/" is stuck in the prefix table with no handle.
  163.  *    Local disk is attached under "/bootTmp".
  164.  *
  165.  *----------------------------------------------------------------------
  166.  */
  167. void
  168. Fs_InitNameSpace()
  169. {
  170.  
  171.     /*
  172.      * Install a crash callback with the recovery module.  (A reboot
  173.      * callback is installed later only if we have to recover with a host.)
  174.      */
  175.  
  176.     Recov_CrashRegister(Fsutil_ClientCrashed, (ClientData) NIL);
  177.  
  178.     /*
  179.      * This is the initial step in boot-strapping the name space.  Place
  180.      * an entry for "/" in the prefix table.  The NIL token will cause a
  181.      * broadcast to get a valid token the first time the prefix is used.
  182.      */
  183.     (void)Fsprefix_Install("/", (Fs_HandleHeader *)NIL, -1, FSPREFIX_IMPORTED); 
  184.  
  185.     fsutil_Initialized = TRUE;
  186. }
  187.  
  188.  
  189. /*
  190.  *----------------------------------------------------------------------
  191.  *
  192.  * Fs_Bin --
  193.  *
  194.  *    Setup objects to be binned.
  195.  *
  196.  * Results:
  197.  *    None.
  198.  *
  199.  * Side effects:
  200.  *    None.
  201.  *
  202.  *----------------------------------------------------------------------
  203.  */
  204. void
  205. Fs_Bin()
  206. {
  207.     Fsio_Bin();
  208.     Fspdev_Bin();
  209.     Fsrmt_Bin();
  210. #ifdef INET
  211.     FsSocketBin();
  212. #endif
  213.     Mem_Bin(FS_BLOCK_SIZE);
  214. }
  215.  
  216.  
  217. /*
  218.  *----------------------------------------------------------------------
  219.  *
  220.  * Fs_ProcInit
  221.  *
  222.  *    Initialize the filesystem part of the process table entry.
  223.  *    This has to be called after Proc_InitMainProc
  224.  *    because of the call to Proc_GetCurrentProc.
  225.  *    It should be done after Rpc_Start because it might do
  226.  *    an RPC to open the current directory.
  227.  *
  228.  * Results:
  229.  *    None.
  230.  *
  231.  * Side effects:
  232.  *    The current directory is opened for the main process.
  233.  *    The permissions mask is initialized.
  234.  *    The table of open file Id's is cleared to zero size.
  235.  *
  236.  *----------------------------------------------------------------------
  237.  */
  238. void
  239. Fs_ProcInit()
  240. {
  241.     ReturnStatus    status;        /* General status code return */
  242.     Proc_ControlBlock    *procPtr;    /* Main process's proc table entry */
  243.     register Fs_ProcessState    *fsPtr;    /* FS state ref'ed from proc table */
  244.     Fs_Stream        *stream;
  245.     Fs_Device         *defaultDiskPtr;
  246.     int            i;
  247.     int            argc;
  248.     char        *argv[10];
  249.     char        argBuffer[256];
  250.     int            numDefaults;
  251.     Boolean        stdDefaults;
  252.     Time        waitTime;
  253.     Time        incrTime;
  254.  
  255.     procPtr = Proc_GetCurrentProc();
  256.     procPtr->fsPtr = fsPtr = mnew(Fs_ProcessState);
  257.     /*
  258.      * General filesystem initialization.
  259.      * Find out how much we can transfer with the RPC system.
  260.      * (If these are already set we don't change them.)
  261.      *
  262.      * FIX THIS TO CALL DOMAIN SPECIFIC INITIALIZATION ROUTINES.
  263.      */
  264.     if (fsMaxRpcDataSize + fsMaxRpcParamSize == 0) {
  265.     Rpc_MaxSizes(&fsMaxRpcDataSize, &fsMaxRpcParamSize);
  266.     }
  267.  
  268.     fsPtr->numGroupIDs    = 1;
  269.     fsPtr->groupIDs     = (int *) malloc(1 * sizeof(int));
  270.     fsPtr->groupIDs[0]    = 0;
  271.  
  272.     defaultDiskPtr = (Fs_Device *) malloc(sizeof(Fs_Device));
  273.     numDefaults = devNumDefaultDiskPartitions;
  274.     stdDefaults = TRUE;
  275.     argc = Mach_GetBootArgs(10, 256, argv, argBuffer);
  276.     for (i = 0; i < argc; i++) {
  277.     if (!strcasecmp(argv[i], "-rootdisk")) {
  278.         if (argc == i + 1) {
  279.         printf("-rootdisk option requires an argument\n");
  280.         } else {
  281.         int    type;
  282.         int    unit;
  283.         int    n;
  284.         n = sscanf(argv[i+1], " %d.%d ", &type, &unit);
  285.         if (n != 2) {
  286.             printf("-rootdisk has the following syntax:\n");
  287.             printf("\t-rootdisk <type>.<unit>\n");
  288.         } else {
  289.             printf("Found -rootdisk option, %d.%d\n",
  290.             type, unit);
  291.             defaultDiskPtr->serverID = -1;
  292.             defaultDiskPtr->type = type;
  293.             defaultDiskPtr->unit = unit;
  294.             defaultDiskPtr->data = (ClientData) NIL;
  295.             numDefaults = 1;
  296.             stdDefaults = FALSE;
  297.         }
  298.         }
  299.     }
  300.     }
  301.     for (i=0 ; i< numDefaults ; i++) {
  302.     /*
  303.      * Second step in bootstrapping the name space.  Try and attach
  304.      * a disk. 
  305.      */
  306.     if (stdDefaults) {
  307.         *defaultDiskPtr = devFsDefaultDiskPartitions[i];
  308.     }
  309.  
  310.     status = Fsdm_AttachDisk(defaultDiskPtr, LOCAL_DISK_NAME, 
  311.             FS_ATTACH_LOCAL | FS_DEFAULT_DOMAIN);
  312.     if (status == SUCCESS) {
  313.         Fs_Attributes    attr;
  314.         int            i;
  315.         char        buffer[128];
  316.         Boolean        rootServer = FALSE;
  317.  
  318.         (void) sprintf(buffer, "%s/ROOT", LOCAL_DISK_NAME);
  319.         status = Fs_GetAttributes(buffer, FS_ATTRIB_FILE, &attr);
  320.         if (status != SUCCESS) {
  321.         printf("Stat of %s returned 0x%x\n", buffer, status);
  322.         rootServer = FALSE;
  323.         } else {
  324.         printf("Found %s.\n", buffer);
  325.         rootServer = TRUE;
  326.         }
  327.         argc = Mach_GetBootArgs(10, 256, argv, argBuffer);
  328.         for (i = 0; i < argc; i++) {
  329.         if (!strcasecmp(argv[i], "-backup")) {
  330.             printf("Found %s option.\n", argv[i]);
  331.             rootServer = FALSE;
  332.         }
  333.         if (!strcasecmp(argv[i], "-root")) {
  334.             printf("Found %s option.\n", argv[i]);
  335.             rootServer = TRUE;
  336.         }
  337.         }
  338.         if (rootServer) {
  339.         (void) sprintf(buffer, "%s/boot", LOCAL_DISK_NAME);
  340.         status = Fs_GetAttributes(buffer, FS_ATTRIB_FILE, &attr);
  341.         if (status != SUCCESS) {
  342.             printf("/boot not found on root partition.\n");
  343.             rootServer = FALSE;
  344.         } else {
  345.             printf("Found /boot.\n");
  346.             if ((attr.type != FS_DIRECTORY)) {
  347.             printf("/boot is not a directory!\n");
  348.             rootServer = FALSE;
  349.             } else {
  350.             printf("/boot is a directory.\n");
  351.             }
  352.         }
  353.         }
  354.         if (rootServer) {
  355.         Fs_Stream        *streamPtr;
  356.         static char        rootPrefix[] = "/";
  357.     
  358.         status = Fs_Open(LOCAL_DISK_NAME, FS_READ|FS_FOLLOW,
  359.                 FS_DIRECTORY, 0, &streamPtr);
  360.         if (status != SUCCESS) {
  361.             printf("Fs_ProcInit: Unable to open local disk prefix!");
  362.         } else {
  363.             printf("Installing the local disk as %s.\n", rootPrefix);
  364.             (void)Fsprefix_Install(rootPrefix, streamPtr->ioHandlePtr, 
  365.                 FS_LOCAL_DOMAIN, 
  366.                 FSPREFIX_LOCAL|FSPREFIX_IMPORTED|FSPREFIX_OVERRIDE);
  367.             if (strcmp(LOCAL_DISK_NAME, rootPrefix)) {
  368.             printf("Clearing local disk prefix %s.\n", 
  369.                 LOCAL_DISK_NAME);
  370.             Fsprefix_Clear(LOCAL_DISK_NAME, TRUE, FALSE);
  371.             }
  372.             fsDiskAttached = TRUE;
  373.             break;
  374.         }
  375.         } else {
  376.         printf("Detaching the local disk.\n");
  377.         Fsdm_DetachDisk(LOCAL_DISK_NAME);
  378.         }
  379.     }
  380.     }
  381.     /*
  382.      * Try and open /.
  383.      */
  384.     status = FAILURE;
  385.     Time_Multiply(time_OneSecond, 5, &incrTime);
  386.     waitTime = time_ZeroSeconds;
  387.     do {
  388.     if (Time_LT(waitTime, time_OneMinute)) {
  389.         Time_Add(waitTime, incrTime, &waitTime);
  390.     }
  391.     status = Fs_Open("/", FS_READ, FS_DIRECTORY, 0, &stream);
  392.     if (status != SUCCESS) {
  393.         if (fsDiskAttached) {
  394.         panic(
  395.         "Fs_ProcInit: I'm the root server and I can't open \"/\".\n");
  396.         }
  397.         /*
  398.          *  Wait a bit and retry the open of "/".
  399.          */
  400.         printf("Can't find server for \"/\", waiting %d seconds.\n",
  401.         waitTime.seconds);
  402.         (void)Sync_WaitTime(waitTime);
  403.     }
  404.     } while (status != SUCCESS);
  405.     Fs_Close(stream);
  406.     fsPtr->cwdPtr = (Fs_Stream *)NIL;
  407.     status = Fs_Open("/boot", FS_READ, FS_DIRECTORY, 0, &fsPtr->cwdPtr);
  408.     if (status != SUCCESS) {
  409.     panic("Fs_ProcInit: can't open /boot.\n");
  410.     }
  411.     /*
  412.      * Set the default permissions mask; it indicates the maximal set of
  413.      * permissions that a newly created file can have.
  414.      */
  415.     fsPtr->filePermissions = FS_OWNER_WRITE |
  416.             FS_OWNER_READ | FS_OWNER_EXEC |
  417.             FS_GROUP_READ | FS_GROUP_EXEC |
  418.             FS_WORLD_READ | FS_WORLD_EXEC;
  419.     /*
  420.      * The open file list is only needed by user processes.
  421.      * Fs_GetNewID will create and grow this list.
  422.      */
  423.     fsPtr->numStreams = 0;
  424.     fsPtr->streamList = (Fs_Stream **)NIL;
  425.     if (!fsDiskAttached) {
  426.     free((char *) defaultDiskPtr);
  427.     }
  428.     return;
  429. }
  430.  
  431.  
  432. /*
  433.  *----------------------------------------------------------------------
  434.  *
  435.  * Fs_InheritState -
  436.  *
  437.  *    This is called during process creation to have the new process
  438.  *    inherit filesystem state from its parent.  This includes the
  439.  *    current directory, open files, and the permission mask.
  440.  *
  441.  * Results:
  442.  *    None.
  443.  *
  444.  * Side effects:
  445.  *    Duplicate the filesystem state of the parent process in
  446.  *    the new child process.
  447.  *
  448.  *----------------------------------------------------------------------
  449.  */
  450. void
  451. Fs_InheritState(parentProcPtr, newProcPtr)
  452.     Proc_ControlBlock *parentProcPtr;    /* Process to inherit from */
  453.     Proc_ControlBlock *newProcPtr;    /* Process that inherits */
  454. {
  455.     register     Fs_ProcessState    *parFsPtr;
  456.     register     Fs_ProcessState    *newFsPtr;
  457.     register    Fs_Stream    **parStreamPtrPtr;
  458.     register    Fs_Stream    **newStreamPtrPtr;
  459.     register     int         i;
  460.     int             len;
  461.  
  462.     /*
  463.      * User Ids.  This is kept separate from the file system state
  464.      * so other modules can use the user ID for permission checking.
  465.      */
  466.     newProcPtr->userID = parentProcPtr->userID;
  467.  
  468.     /*
  469.      * The rest of the filesystem state hangs off the Fs_ProcessState record.
  470.      * This could be shared after forking to implement file descriptor sharing.
  471.      */
  472.     parFsPtr = parentProcPtr->fsPtr;
  473.     newProcPtr->fsPtr = newFsPtr = mnew(Fs_ProcessState);
  474.  
  475.     /*
  476.      * Current working directory.
  477.      */
  478.     if (parFsPtr->cwdPtr != (Fs_Stream *)NIL) {
  479.     Fsio_StreamCopy(parFsPtr->cwdPtr, &newFsPtr->cwdPtr);
  480.     } else {
  481.     newFsPtr->cwdPtr = (Fs_Stream *)NIL;
  482.     }
  483.  
  484.     /*
  485.      * Open stream list.
  486.      */
  487.     len = newFsPtr->numStreams = parFsPtr->numStreams;
  488.     if (len > 0) {
  489.     newFsPtr->streamList =
  490.         (Fs_Stream **)malloc(len * sizeof(Fs_Stream *));
  491.     newFsPtr->streamFlags = (char *)malloc(len * sizeof(char));
  492.     for (i = 0, parStreamPtrPtr = parFsPtr->streamList,
  493.             newStreamPtrPtr = newFsPtr->streamList;
  494.          i < len;
  495.          i++, parStreamPtrPtr++, newStreamPtrPtr++) {
  496.         if (*parStreamPtrPtr != (Fs_Stream *) NIL) {
  497.         Fsio_StreamCopy(*parStreamPtrPtr, newStreamPtrPtr);
  498.         newFsPtr->streamFlags[i] = parFsPtr->streamFlags[i];
  499.         } else {
  500.         *newStreamPtrPtr = (Fs_Stream *) NIL;
  501.         newFsPtr->streamFlags[i] = 0;
  502.         }
  503.     }
  504.     } else {
  505.     newFsPtr->streamList = (Fs_Stream **)NIL;
  506.     newFsPtr->streamFlags = (char *)NIL;
  507.     }
  508.  
  509.     newFsPtr->filePermissions = parFsPtr->filePermissions;
  510.  
  511.     /*
  512.      * Group ID list.
  513.      */
  514.  
  515.     len = newFsPtr->numGroupIDs = parFsPtr->numGroupIDs;
  516.     if (len) {
  517.     newFsPtr->groupIDs = (int *)malloc(len * sizeof(int));
  518.     for (i=0 ; i<len; i++) {
  519.         newFsPtr->groupIDs[i] = parFsPtr->groupIDs[i];
  520.     }
  521.     } else {
  522.     newFsPtr->groupIDs = (int *)NIL;
  523.     }
  524. }
  525.  
  526. /*
  527.  *----------------------------------------------------------------------
  528.  *
  529.  * Fs_CloseOnExec -
  530.  *
  531.  *    Called just before a process overlays itself with another program.
  532.  *    At this point any stream marked as CLOSE_ON_EXEC are closed.
  533.  *    This makes cleanup for shell-like programs easier.
  534.  *
  535.  * Results:
  536.  *    None.
  537.  *
  538.  * Side effects:
  539.  *    Close streams marked CLOSE_ON_EXEC.
  540.  *
  541.  *----------------------------------------------------------------------
  542.  */
  543. void
  544. Fs_CloseOnExec(procPtr)
  545.     Proc_ControlBlock *procPtr;        /* Process to operate on */
  546. {
  547.     register int i;
  548.     register Fs_Stream **streamPtrPtr;    /* Pointer into streamList */
  549.     char *flagPtr;            /* Pointer into flagList */
  550.  
  551.     for (i=0, streamPtrPtr = procPtr->fsPtr->streamList,
  552.           flagPtr = procPtr->fsPtr->streamFlags ;
  553.      i < procPtr->fsPtr->numStreams ;
  554.      i++, streamPtrPtr++, flagPtr++) {
  555.     if ((*streamPtrPtr != (Fs_Stream *)NIL) &&
  556.         (*flagPtr & FS_CLOSE_ON_EXEC)) {
  557.         (void)Fs_Close(*streamPtrPtr);
  558.         *streamPtrPtr = (Fs_Stream *)NIL;
  559.     }
  560.     }
  561. }
  562.  
  563. /*
  564.  *----------------------------------------------------------------------
  565.  *
  566.  * Fs_CloseState
  567.  *
  568.  *    This is called when a process dies to clean up its filesystem state
  569.  *    by closing its open files and its current directory.
  570.  *
  571.  *    Note: to avoid problems when destroying a process, we have two
  572.  *    phases of removing file system state.  Phase 0 will just close
  573.  *    streams.  Phase 1 closes down everything.  (Phase 0 is optional.)
  574.  *
  575.  * Results:
  576.  *    None.
  577.  *
  578.  * Side effects:
  579.  *    All its open streams are closed, and associated memory is free'd.
  580.  *
  581.  *----------------------------------------------------------------------
  582.  */
  583. void
  584. Fs_CloseState(procPtr, phase)
  585.     Proc_ControlBlock *procPtr;        /* An exiting process to clean up */
  586.     int phase;                /* 0 for start, 1 for total. */
  587. {
  588.     register int i;
  589.     register Fs_ProcessState *fsPtr = procPtr->fsPtr;
  590.  
  591.     if (phase>0) {
  592.     if (fsPtr->cwdPtr != (Fs_Stream *) NIL) {
  593.         (void)Fs_Close(fsPtr->cwdPtr);
  594.     }
  595.     }
  596.  
  597.     if (fsPtr->streamList != (Fs_Stream **)NIL) {
  598.     for (i=0 ; i < fsPtr->numStreams ; i++) {
  599.         register Fs_Stream *streamPtr;
  600.  
  601.         streamPtr = fsPtr->streamList[i];
  602.         if (streamPtr != (Fs_Stream *)NIL) {
  603.         (void)Fs_Close(streamPtr);
  604.         }
  605.     }
  606.     free((Address) fsPtr->streamList);
  607.     free((Address) fsPtr->streamFlags);
  608.     fsPtr->streamList = (Fs_Stream **)NIL;
  609.     }
  610.  
  611.     if (phase>0) {
  612.     if (fsPtr->groupIDs != (int *) NIL) {
  613.         free((Address) fsPtr->groupIDs);
  614.         fsPtr->groupIDs = (int *) NIL;
  615.         fsPtr->numGroupIDs = 0;
  616.     }
  617.     free((Address)fsPtr);
  618.     procPtr->fsPtr = (Fs_ProcessState *)NIL;
  619.     }
  620. }
  621.